/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.acceptance.util.worker;

import java.io.File;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.easymock.classextension.EasyMock;
import org.ourgrid.acceptance.util.WorkerAcceptanceUtil;
import org.ourgrid.broker.communication.actions.ErrorOcurredMessageHandle;
import org.ourgrid.common.FileTransferInfo;
import org.ourgrid.common.interfaces.Worker;
import org.ourgrid.common.interfaces.WorkerClient;
import org.ourgrid.common.interfaces.to.GridProcessErrorTypes;
import org.ourgrid.common.interfaces.to.MessageHandle;
import org.ourgrid.matchers.ErrorOcurredMessageHandleMatcher;
import org.ourgrid.matchers.FileRecoverErrorMessageMatcher;
import org.ourgrid.worker.WorkerComponent;
import org.ourgrid.worker.WorkerConstants;
import org.ourgrid.worker.business.controller.GridProcessError;
import org.ourgrid.worker.communication.processors.handle.GetFilesMessageHandle;

import br.edu.ufcg.lsd.commune.container.ObjectDeployment;
import br.edu.ufcg.lsd.commune.container.logging.CommuneLogger;
import br.edu.ufcg.lsd.commune.container.servicemanager.FileTransferManager;
import br.edu.ufcg.lsd.commune.context.ModuleContext;
import br.edu.ufcg.lsd.commune.identification.ContainerID;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;
import br.edu.ufcg.lsd.commune.processor.filetransfer.OutgoingTransferHandle;
import br.edu.ufcg.lsd.commune.processor.filetransfer.TransferHandle;
import br.edu.ufcg.lsd.commune.testinfra.AcceptanceTestUtil;


public class Req_081_Util extends WorkerAcceptanceUtil {
	
	public Req_081_Util(ModuleContext context) {
		super(context);
	}

	public void requestToRecoverFilesByUnknownClient(WorkerComponent component, long requestID,
			String senderPubKey, String... filePath) {
		requestToRecoverFile(component, requestID, senderPubKey, null, null, null, null, null, true,
				false, false, false, false, filePath);
	}
	
	public void requestToRecoverFilesWithInvalidVariable(WorkerComponent component, long requestID,
			String senderPubKey, String problematicPath, WorkerClient workerClient, String... filePath) {
		requestToRecoverFilesWithInvalidDirectory(component, requestID, senderPubKey, "Invalid variable found.",
				problematicPath, workerClient, true, filePath);
	}

	public void requestToRecoverFilesWithNotRelativePath(WorkerComponent component, long requestID,
			String senderPubKey, String problematicPath, WorkerClient workerClient, String absolutePath, String... filePath) {
		requestToRecoverFilesWithInvalidDirectory(component, requestID, senderPubKey, "File path is not relative to " +
				absolutePath + " directory.", problematicPath, workerClient, true, filePath);
	}
	
	public void requestToRecoverFilesWithSamePaths(WorkerComponent component, long requestID,
			String senderPubKey, String problematicPath, WorkerClient workerClient, String... filePath) {
		requestToRecoverFilesWithInvalidDirectory(component, requestID, senderPubKey, "There are files with same path.",
				problematicPath, workerClient, false, filePath);
	}
	
	public void requestToRecoverAnInexistentFile(WorkerComponent component, long requestID,
			String senderPubKey, String problematicPath, WorkerClient workerClient, String... filePath) {
		requestToRecoverFilesWithInvalidDirectory(component, requestID, senderPubKey, "File not found.",
				problematicPath, workerClient, false, filePath);
	}

	public void requestToRecoverAnUnreadableFile(WorkerComponent component, long requestID,
			String senderPubKey, String problematicPath, WorkerClient workerClient, String... filePath) {
		requestToRecoverFilesWithInvalidDirectory(component, requestID, senderPubKey, "File cannot be read.",
				problematicPath, workerClient, true, filePath);
	}

	private void requestToRecoverFilesWithInvalidDirectory(WorkerComponent component, long requestID,	String senderPubKey,
			String errorCause, String problematicPath, WorkerClient workerClient, boolean ioError, String... filePath) {
		requestToRecoverFile(component, requestID, senderPubKey, workerClient, errorCause, problematicPath,
				null, null, false, false, true, ioError, false, filePath);
	}
	
	public void requestToRecoverFilesOnUnstartedWorker(WorkerComponent component, long requestID,
			String senderPubKey, String... filePath) {
		requestToRecoverFile(component, requestID, senderPubKey, null, null, null, null, null,
				false, true, false, false, false, filePath);
	}

	public void requestToRecoverFilesBeforeExecutionFinish(WorkerComponent component, long requestID,
			String senderPubKey, String... filePath) {
		requestToRecoverFile(component, requestID, senderPubKey, null, null, null, null, null,
				false, false, false, false, false, filePath);
	}

	public List<TransferHandle> requestToRecoverFilesWithSuccess(WorkerComponent component, long requestID, String senderPubKey, WorkerClient workerClient,
			 String playpenDir, String storageDir, String... filePath) {
		return requestToRecoverFile(component, requestID, senderPubKey, workerClient, null, null, playpenDir, storageDir,
				false, false, true, false, false, filePath);
	}
	
	public void requestToRecoverFilesThatAlreadyBeingUploaded(WorkerComponent component, long requestID, String senderPubKey,
			WorkerClient workerClient, String problematicDir, String playpenDir, String storageDir, String... filePath) {
		requestToRecoverFile(component, requestID, senderPubKey, workerClient, null, problematicDir, playpenDir, storageDir,
				false, false, true, false, false, filePath);
	}
	
	public void requestToRecoverFilesOnWorkerWithError(WorkerComponent component, long requestID, WorkerClient workerClient,
			String senderPubKey, String... filePath) {
		requestToRecoverFile(component, requestID, senderPubKey, workerClient,  null, null, null, null,
				false, false, true, false, true, filePath);
	}

	private List<TransferHandle> requestToRecoverFile(WorkerComponent component, long requestID, String senderPubKey, WorkerClient workerClient,
			String errorCause, String problematicPath, String playpenDir, String storageDir, boolean unknownClient,
			boolean isUnstartedWorker, boolean isExecutionFinish, boolean ioError, boolean isWorkerWithError, String... filePath) {
		
		FileTransferManager fileTransferManager = EasyMock.createMock(FileTransferManager.class);
		component.setFileTransferManager(fileTransferManager);
		
		DeploymentID workerClientDeploymentID = null;
		
		if (workerClient == null) {
			workerClient = EasyMock.createMock(WorkerClient.class);	
			workerClientDeploymentID = new DeploymentID(new ContainerID("mgusername", "mgserver", "mgmodule", senderPubKey), "mygrid");

			createStub(workerClient, WorkerClient.class, workerClientDeploymentID);
		} else {
			EasyMock.reset(workerClient);
		}
		
		CommuneLogger logger = component.getLogger();
		
		Worker worker = getWorker();
		ObjectDeployment workerOD = getWorkerDeployment();
		
		FileTransferInfo[] infos = createInfos(filePath);
		List<TransferHandle> handles = new LinkedList<TransferHandle>();
		
		if (unknownClient) {
				logger.warn("An unknown client tried to recover the files " + Arrays.toString(filePath) + ". Unknown client public key: [" + senderPubKey + "].");
		} else {
			if (isUnstartedWorker) {
				logger.warn("A client tried to recover the files " + Arrays.toString(filePath) + ", but this Worker was not commanded to start " +
						"the work yet. This message was ignored. Client public key: [" + senderPubKey + "].");
			} else if (!isExecutionFinish) {
				logger.warn("A client tried to recover the files " + Arrays.toString(filePath) + ", but the Broker cannot download " +
						"files before the execution finish. This message was ignored. Client public key: [" + senderPubKey + "].");
			} else {
				
				if (isWorkerWithError) {
					logger.warn("A client tried to recover the files " + Arrays.toString(filePath) + ". This message was ignored, because an error already ocurred." +
							" Client public key: [" + senderPubKey + "].");
				} else {
					if (errorCause != null) {
						logger.warn(FileRecoverErrorMessageMatcher.eqMatcher(errorCause, problematicPath, senderPubKey));
						
						if (ioError) {
							workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
									new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.IO_ERROR))));

						} else {
							workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
									new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR))));
						}
					} else {
						
						for (FileTransferInfo info : infos) {
							
							String path = info.getFilePath();
							
							String completePath = path.startsWith("$" + WorkerConstants.ENV_STORAGE)? storageDir + File.separator + 
									path.replace("$" + WorkerConstants.ENV_STORAGE + File.separator, "") : playpenDir + File.separator + 
									path.replace("$" + WorkerConstants.ENV_PLAYPEN + File.separator, "");
							
						    File file = new File(completePath);
							OutgoingTransferHandle outgoingFileTransfer = createOutgoingFileTransfer(info.getTransferHandleID(), 
									workerClientDeploymentID, file.getName(), file);
							handles.add(outgoingFileTransfer);
							
						    if (path.equals(problematicPath)) {
								logger.warn("The client tried to recover the files that already being uploaded." +
												" File path: [" + completePath + "]. Client public key: [" + senderPubKey + "].");
							} else {
								logger.debug("The client tried to recover the files. Worker accepted the transfer request and is starting " +
												"to upload the file. File path: [" + completePath + "]. Handle: " + info.getTransferHandleID() +
												". Client public key: [" + senderPubKey + "].");
								fileTransferManager.startTransfer(outgoingFileTransfer, worker);
							}
						}
					}
				}
			}
		}
			
		EasyMock.replay(fileTransferManager);
		EasyMock.replay(logger);
		EasyMock.replay(workerClient);
		
		MessageHandle handle = new GetFilesMessageHandle(0, infos);
		AcceptanceTestUtil.setExecutionContext(component, workerOD, senderPubKey);
		worker.sendMessage(handle);
		
		EasyMock.verify(logger);
		EasyMock.verify(fileTransferManager);
		
		EasyMock.reset(logger);
		
		return handles;
	}
	
	private FileTransferInfo[] createInfos(String[] filePath) {
		FileTransferInfo[] infos = null;
		if (filePath != null) {
			int handleCont = 1;
			infos =  new FileTransferInfo[filePath.length];
			for (int i = 0; i < filePath.length; i++) {
				infos[i] = new FileTransferInfo(handleCont++, filePath[i]);
			}
		}
		return infos;
	}

	public void receiveFileRejectWithUnknownHandle(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle) {
		receiveFileReject(component, workerClient, clientPublicKey, handle, null, true, false);
	}

	public void receiveFileRejectOnWorkerWithError(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath) {
		receiveFileReject(component, workerClient, clientPublicKey, handle, filePath, false, true);
	}
	
	public void receiveFileReject(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath) {
		receiveFileReject(component, workerClient, clientPublicKey, handle, filePath, false, false);
	}
	
	private void receiveFileReject(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath,
			boolean isUnknownHandle, boolean isWorkerWithError) {
		CommuneLogger oldLogger = component.getLogger();
		CommuneLogger newLogger = EasyMock.createMock(CommuneLogger.class);
		
		component.setLogger(newLogger);
		
		EasyMock.reset(workerClient);
		
		Worker worker = getWorker();

		if (isUnknownHandle) {
			newLogger.warn("The worker received a file reject message with unknown handle. This message was ignored." +
					" Handle: " + handle + ". Client public key: [" + clientPublicKey + "].");
		} else {
			
			if (isWorkerWithError) {
				newLogger.warn("The worker received a file reject message from the client. This message was ignored, because an error already" +
						" ocurred. File path: [" + filePath + "]. Handle: " + handle +
						". Client public key: [" + clientPublicKey + "].");
				workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
						new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR))));
			} else {
				newLogger.error("The worker received a file reject message from the client. This message was successfully accepted. " +
						"File path: [" + filePath + "]. Handle: " + handle + ". Client public key: [" + clientPublicKey + "].");
				workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
						new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR))));
			}
		}

		EasyMock.replay(workerClient);
		EasyMock.replay(newLogger);
		
		worker.transferRejected(handle);
		
		EasyMock.verify(newLogger);
		component.setLogger(oldLogger);
	}
	
	public void receiveFileTransferCancelledWithUnknownHandle(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, long amountDataUploaded) {
		receiveFileTransferCancelled(component, workerClient, clientPublicKey, handle, null, amountDataUploaded, true, false);
	}

	public void receiveFileTransferCancelledOnWorkerWithError(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded) {
		receiveFileTransferCancelled(component, workerClient, clientPublicKey, handle, filePath, amountDataUploaded, false, true);
	}
	
	public void receiveFileTransferCancelled(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded) {
		receiveFileTransferCancelled(component, workerClient, clientPublicKey, handle, filePath, amountDataUploaded, false, false);
	}
	
	private void receiveFileTransferCancelled(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath,
			long amountDataUploaded, boolean isUnknownHandle, boolean isWorkerWithError) {
		CommuneLogger oldLogger = component.getLogger();
		CommuneLogger newLogger = EasyMock.createMock(CommuneLogger.class);
		
		component.setLogger(newLogger);
		
		EasyMock.reset(workerClient);
		
		Worker worker = getWorker();

		if (isUnknownHandle) {
			newLogger.warn("The worker received an outgoing transfer cancelled message with unknown handle. This message was ignored." +
					" Handle: " + handle + ". Amount of data uploaded: " + amountDataUploaded + " bytes. Client public key: [" +
					clientPublicKey + "].");
		} else {
			
			if (isWorkerWithError) {
				newLogger.warn("The worker received an outgoing transfer cancelled message from the client. This message was ignored," +
						" because a error already ocurred. File path: [" + filePath + "]. Handle: " + handle + ". Amount of data uploaded: " +
								amountDataUploaded + " bytes. Client public key: [" + clientPublicKey + "].");
				workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
						new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR))));
			} else {
				newLogger.error("The worker received an outgoing transfer cancelled message from the client. This message was successfully accepted. " +
						"File path: [" + filePath + "]. Handle: " + handle + ". Amount of data uploaded: " + amountDataUploaded + " bytes. " +
						"Client public key: [" + clientPublicKey + "].");
				workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
						new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR))));
			}
		}

		EasyMock.replay(workerClient);
		EasyMock.replay(newLogger);
		
		worker.outgoingTransferCancelled(handle, amountDataUploaded);
		
		EasyMock.verify(newLogger);
		component.setLogger(oldLogger);
	}

	public void receiveFileTransferFailedWithUnknownHandle(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, long amountDataUploaded, Exception failCause) {
		receiveFileTransferFailed(component, workerClient, clientPublicKey, handle, null, amountDataUploaded, failCause, true, false);
	}

	public void receiveFileTransferFailedOnWorkerWithError(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded, Exception failCause) {
		receiveFileTransferFailed(component, workerClient, clientPublicKey, handle, filePath, amountDataUploaded, failCause, false, true);
	}
	
	public void receiveFileTransferFailed(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded, Exception failCause) {
		receiveFileTransferFailed(component, workerClient, clientPublicKey, handle, filePath, amountDataUploaded, failCause, false, false);
	}
	
	private void receiveFileTransferFailed(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath,
			long amountDataUploaded, Exception failCause, boolean isUnknownHandle, boolean isWorkerWithError) {
		CommuneLogger oldLogger = component.getLogger();
		CommuneLogger newLogger = EasyMock.createMock(CommuneLogger.class);
		
		component.setLogger(newLogger);
		
		EasyMock.reset(workerClient);
		
		Worker worker = getWorker();

		if (isUnknownHandle) {
			newLogger.warn("The worker received an outgoing transfer failed message with unknown handle. This message was ignored." +
					" Handle: " + handle + ". Amount of data uploaded: " + amountDataUploaded + " bytes. Client public key: [" +
					clientPublicKey + "].");
		} else {
			
			if (isWorkerWithError) {
				newLogger.warn("The worker received an outgoing transfer failed message from the client. This message was ignored," +
						" because an error already ocurred. File path: [" + filePath + "]. Handle: " + handle + ". Amount of data uploaded: " +
								amountDataUploaded + " bytes. Client public key: [" + clientPublicKey + "].");
				workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
						new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR))));
			} else {
				newLogger.error("The worker received an outgoing transfer failed message from the client. This message was successfully accepted. " +
						"File path: [" + filePath + "]. Handle: " + handle + ". Amount of data uploaded: " + amountDataUploaded + " bytes. " +
						"Client public key: [" + clientPublicKey + "].");
				workerClient.sendMessage(ErrorOcurredMessageHandleMatcher.eqMatcher(
						new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR))));
			}
		}

		EasyMock.replay(workerClient);
		EasyMock.replay(newLogger);
		
		worker.outgoingTransferFailed(handle, failCause, amountDataUploaded);
		
		EasyMock.verify(newLogger);
		component.setLogger(oldLogger);
	}
	
	public void receiveFileTransferCompletedWithUnknownHandle(WorkerComponent component, WorkerClient workerClient, String clientPublicKey,
			OutgoingTransferHandle handle,
			long amountDataUploaded) {
		receiveFileTransferCompleted(component, workerClient, clientPublicKey, handle, null, amountDataUploaded, true, false, false);
	}

	public void receiveFileTransferCompletedOnWorkerWithError(WorkerComponent component, WorkerClient workerClient, String clientPublicKey,
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded) {
		receiveFileTransferCompleted(component, workerClient, clientPublicKey, handle, filePath, amountDataUploaded, false, true, false);
	}
	
	public void receiveFileTransferCompleted(WorkerComponent component, WorkerClient workerClient, String clientPublicKey,
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded) {
		receiveFileTransferCompleted(component, workerClient, clientPublicKey, handle, filePath, amountDataUploaded, false, false, false);
	}
	
	public void receiveFileTransferCompletedForLastUploadingFile(WorkerComponent component, WorkerClient workerClient, String clientPublicKey,
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded) {
		receiveFileTransferCompleted(component, workerClient, clientPublicKey, handle, filePath, amountDataUploaded, false, false, true);
	}
	
	private void receiveFileTransferCompleted(WorkerComponent component, WorkerClient workerClient, String clientPublicKey, 
			OutgoingTransferHandle handle, String filePath, long amountDataUploaded, boolean isUnknownHandle, boolean isWorkerWithError, 
			boolean allUploadsFinish) {
		CommuneLogger oldLogger = component.getLogger();
		CommuneLogger newLogger = EasyMock.createMock(CommuneLogger.class);
		
		component.setLogger(newLogger);
		
		EasyMock.reset(workerClient);
		
		Worker worker = getWorker();
		if (isUnknownHandle) {
			newLogger.warn("The worker received an outgoing transfer completed message with unknown handle. This message was ignored." +
					" Handle: " + handle + ". Amount of data uploaded: " + amountDataUploaded + " bytes. Client public key: [" +
					clientPublicKey + "].");
		} else {
			
			if (isWorkerWithError) {
				newLogger.warn("The worker received an outgoing transfer completed message from the client. This message was ignored," +
						" because an error already ocurred. File path: [" + filePath + "]. Handle: " + handle + ". Amount of data uploaded: " +
								amountDataUploaded + " bytes. Client public key: [" + clientPublicKey + "].");
				workerClient.sendMessage(new ErrorOcurredMessageHandle(new GridProcessError(GridProcessErrorTypes.APPLICATION_ERROR)));
			} else {
				newLogger.debug("The worker received an outgoing transfer completed message from the client. This message was successfully accepted. " +
						"File path: [" + filePath + "]. Handle: " + handle + ". Amount of data uploaded: " + amountDataUploaded + " bytes. " +
						"Client public key: [" + clientPublicKey + "].");
				
				if (allUploadsFinish) {
					newLogger.info("All current uploading files has been finished.");
				}
			}
		}

		EasyMock.replay(workerClient);
		EasyMock.replay(newLogger);
		
		worker.outgoingTransferCompleted(handle, amountDataUploaded);
		
		EasyMock.verify(newLogger);
		component.setLogger(oldLogger);
	}


}
